
package com.hlkj.pay.app.merchant.ext.impl.wxpay;
/*
 * Hlpay-Plus aggregate payment system.
 * Copyright (c) 2024-2025 Hlpay Team Copyright has the right of final interpretation.
 */

import java.time.format.DateTimeFormatter;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;

import com.hlkj.framework.common.pojo.CommonResult;
import com.hlkj.framework.common.util.json.JsonUtils;
import com.hlkj.framework.extension.annotation.Extension;
import com.hlkj.framework.extension.executor.ExtensionExecutor;
import com.hlkj.framework.extension.scenario.BizScenario;
import com.hlkj.pay.app.merchant.ext.ChannelMerchantOrderExtPt;
import com.hlkj.pay.app.merchant.ext.MerchantExtConstant;
import com.hlkj.pay.app.merchant.ext.dto.order.ChannelPayOrderContext;
import com.hlkj.pay.app.merchant.ext.dto.order.ChannelPayOrderQueryContext;
import com.hlkj.pay.app.merchant.ext.dto.order.ChannelRefundPayOrderContext;
import com.hlkj.pay.app.merchant.ext.impl.wxpay.constant.WxPayExtConstant;
import com.hlkj.pay.app.merchant.ext.impl.wxpay.dto.WxNotifyResp;
import com.hlkj.pay.app.merchant.ext.impl.wxpay.dto.WxPayConfig;
import com.hlkj.pay.app.merchant.ext.impl.wxpay.dto.WxRefundNotifyResp;
import com.hlkj.pay.app.merchant.ext.impl.wxpay.handler.WxPayCommonParamsHandle;
import com.hlkj.pay.common.MerchantResultCode;
import com.hlkj.pay.dto.order.PayOrderChannelResultDto;
import com.hlkj.pay.dto.order.notify.CsOrderNotifyDto;
import com.hlkj.pay.dto.order.notify.RefundOrderNotifyDto;
import com.hlkj.pay.enums.PayEnums;
import com.hlkj.pay.enums.PayOrderEnums;
import com.hlkj.pay.infrastructure.model.order.PayOrderDO;
import com.hlkj.pay.infrastructure.model.order.RefundOrderDO;
import com.hlkj.pay.infrastructure.model.pay.PayWayDO;
import com.hlkj.pay.infrastructure.model.pay.ProviderChannelConfigDO;
import com.hlkj.pay.util.DateUtils;
import com.hlkj.pay.util.HttpRequestUtil;
import com.hlkj.pay.util.MerchantUtils;
import com.hlkj.pay.vo.order.req.PayOrderCommonParamReq;
import com.ijpay.core.kit.WxPayKit;

import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * 微信订单操作
 * 
 * @author HlpayTeam
 * @date 2024/10/10 13:51
 */
@Slf4j
@Repository
@RequiredArgsConstructor
@Extension(bizId = MerchantExtConstant.CHANNEL_WX_PAY)
public class WxMerchantOrderExtPt implements ChannelMerchantOrderExtPt {

    private final ExtensionExecutor extensionExecutor;

    private final WxPayCommonParamsHandle wxPayCommonParamsHandle;

    @Override
    public CommonResult<String> channelOrderCode(ChannelPayOrderContext channelPayOrderContext) {
        return CommonResult.success();
    }

    @Override
    public void submitChannelPayOrder(ChannelPayOrderContext channelPayOrderContext) {
        PayWayDO payWayDO = channelPayOrderContext.getPayWayDO();
        String wxPay = wxPay(payWayDO);
        if (!StringUtils.hasText(wxPay)) {
            channelPayOrderContext.setRemark("当前支付方式不支持");
            channelPayOrderContext.setProcessStatus(Boolean.FALSE);
            return;
        }
        extensionExecutor.executeVoid(WxScenePayOrderExtPt.class, BizScenario.valueOf(wxPay), ex -> ex.submitChannelPayOrder(channelPayOrderContext));
    }

    @Override
    public void queryChannelPayOrder(ChannelPayOrderQueryContext channelPayOrderQueryContext) {
        PayWayDO payWayDO = channelPayOrderQueryContext.getPayWayDO();
        String wxPay = wxPay(payWayDO);
        if (!StringUtils.hasText(wxPay)) {
            channelPayOrderQueryContext.setRemark("当前支付方式不支持");
            channelPayOrderQueryContext.setProcessStatus(Boolean.FALSE);
            return;
        }
        extensionExecutor.executeVoid(WxScenePayOrderExtPt.class, BizScenario.valueOf(wxPay), ex -> ex.queryChannelPayOrder(channelPayOrderQueryContext));
    }

    @Override
    public void closeChannelPayOrder(ChannelPayOrderQueryContext channelPayOrderQueryContext) {
        PayWayDO payWayDO = channelPayOrderQueryContext.getPayWayDO();
        String wxPay = wxPay(payWayDO);
        if (!StringUtils.hasText(wxPay)) {
            channelPayOrderQueryContext.setRemark("当前支付方式不支持");
            channelPayOrderQueryContext.setProcessStatus(Boolean.FALSE);
            return;
        }
        extensionExecutor.executeVoid(WxScenePayOrderExtPt.class, BizScenario.valueOf(wxPay), ex -> ex.closeChannelPayOrder(channelPayOrderQueryContext));
    }

    @Override
    public void queryChannelClosePayOrder(ChannelPayOrderQueryContext channelPayOrderQueryContext) {
        PayWayDO payWayDO = channelPayOrderQueryContext.getPayWayDO();
        String wxPay = wxPay(payWayDO);
        if (!StringUtils.hasText(wxPay)) {
            channelPayOrderQueryContext.setRemark("当前支付方式不支持");
            channelPayOrderQueryContext.setProcessStatus(Boolean.FALSE);
            return;
        }
        extensionExecutor.executeVoid(WxScenePayOrderExtPt.class, BizScenario.valueOf(wxPay), ex -> ex.queryChannelClosePayOrder(channelPayOrderQueryContext));

    }

    @Override
    public boolean refundApply(ChannelRefundPayOrderContext channelRefundPayOrderContext) {
        return true;
    }

    @Override
    public void refundChannelPayOrder(ChannelRefundPayOrderContext channelRefundPayOrderContext) {
        PayWayDO payWayDO = channelRefundPayOrderContext.getPayWayDO();
        String wxPay = wxPay(payWayDO);
        if (!StringUtils.hasText(wxPay)) {
            channelRefundPayOrderContext.setRemark("当前支付方式不支持");
            channelRefundPayOrderContext.setProcessStatus(Boolean.FALSE);
            return;
        }
        extensionExecutor.executeVoid(WxScenePayOrderExtPt.class, BizScenario.valueOf(wxPay), ex -> ex.refundChannelPayOrder(channelRefundPayOrderContext));
    }

    @Override
    public void queryRefundChannelPayOrder(ChannelRefundPayOrderContext channelRefundPayOrderContext) {
        PayWayDO payWayDO = channelRefundPayOrderContext.getPayWayDO();
        String wxPay = wxPay(payWayDO);
        if (!StringUtils.hasText(wxPay)) {
            channelRefundPayOrderContext.setRemark("当前支付方式不支持");
            channelRefundPayOrderContext.setProcessStatus(Boolean.FALSE);
            return;
        }
        extensionExecutor.executeVoid(WxScenePayOrderExtPt.class, BizScenario.valueOf(wxPay), ex -> ex.queryRefundChannelPayOrder(channelRefundPayOrderContext));
    }

    @Override
    public CommonResult<PayOrderChannelResultDto> orderStatusParse(HttpServletRequest request, ProviderChannelConfigDO providerChannelConfigDO) {

        CommonResult<PayOrderChannelResultDto> payOrderDOCommonResult = CommonResult.success();
        try {
            String paramsConfig = providerChannelConfigDO.getParamsConfig();
            // 微信配置信息
            WxPayConfig wxPayConfig = JsonUtils.parseObject(paramsConfig, WxPayConfig.class);

            String timestamp = request.getHeader("Wechatpay-Timestamp");
            String nonce = request.getHeader("Wechatpay-Nonce");
            String serialNo = request.getHeader("Wechatpay-Serial");
            String signature = request.getHeader("Wechatpay-Signature");
            log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
            String result = HttpRequestUtil.requestBodyParams(request);
            log.info("微信支付通知密文 {}", result);
            String plainText = null;
            // 微信公钥验证签名并解密
            if(serialNo.equals(wxPayConfig.getPayPublicId())){
                plainText = WxPayKit.verifyPublicKeyNotify(result, signature, nonce, timestamp,
                        wxPayConfig.getApiV3Key(), wxPayConfig.getPayPublicPath());
            }else if(serialNo.equals(wxPayCommonParamsHandle.getPlatSerialNumber(wxPayConfig))){
                plainText = WxPayKit.verifyNotify(serialNo,result, signature, nonce, timestamp,wxPayConfig.getApiV3Key(), wxPayConfig.getPlatformCertPath());
            }

            log.info("微信支付通知明文 {}", plainText);

            if (StrUtil.isEmpty(plainText)) {
                log.info("微信回调验签不通过,signature:{}", signature);
                payOrderDOCommonResult.setCode(MerchantResultCode.MERCHANT_CHANNEL_CALLBACK_SIGN_ERROR.getCode());
            }
            Map<String, Object> paramsMap = JsonUtils.parseObject(plainText, Map.class);
            if (paramsMap.containsKey("out_trade_no")) {
                payOrderDOCommonResult.setData(commonParse(plainText));
            }

        }
        catch (Exception e) {
            e.printStackTrace();
            payOrderDOCommonResult.setCode(MerchantResultCode.MERCHANT_CHANNEL_CALLBACK_SIGN_ERROR.getCode());
        }

        log.info("微信收到回调,解析后的订单数据为 payOrderDO:{}", JsonUtils.toJsonStringNotNull(payOrderDOCommonResult.getData()));
        return payOrderDOCommonResult;
    }

    PayOrderChannelResultDto commonParse(String params) {
        PayOrderDO updartePayOrderDO = new PayOrderDO();
        WxNotifyResp respData = JsonUtils.parseObject(params, WxNotifyResp.class);
        switch (respData.getTrade_state()) {
            case "NOTPAY":
            case "USERPAYING":
                updartePayOrderDO.setState(PayOrderEnums.PAY_ORDER_STATUS.PAYING.getCode());
                updartePayOrderDO.setBankMsg("");
                break;
            case "REVOKED":
                updartePayOrderDO.setState(PayOrderEnums.PAY_ORDER_STATUS.CANCEL.getCode());
                break;
            case "REFUND":
            case "SUCCESS":
                updartePayOrderDO.setState(PayOrderEnums.PAY_ORDER_STATUS.SUCCESS.getCode());
                updartePayOrderDO.setBankMsg("");
                break;
            case "PAYERROR":
                updartePayOrderDO.setState(PayOrderEnums.PAY_ORDER_STATUS.FAIL.getCode());
                break;
            case "CLOSED":
                updartePayOrderDO.setState(PayOrderEnums.PAY_ORDER_STATUS.CLOSE.getCode());
                break;

        }
        if (StringUtils.hasText(respData.getSuccess_time())) {
            try {
                updartePayOrderDO.setSuccessTime(DateUtils.shanghaiDate(respData.getSuccess_time(), DateTimeFormatter.ISO_OFFSET_DATE_TIME));
            }
            catch (Exception e) {
                log.error("微信订单回调返回 时间格式化错误 dateTime:{},message:{}", respData.getSuccess_time(), e.getMessage());
            }
        }
        updartePayOrderDO.setPayOrderNo(respData.getOut_trade_no());
        updartePayOrderDO.setChannelOrderNo(respData.getTransaction_id());
        if (respData.getAmount() != null) {
            updartePayOrderDO.setSettlementAmount(MerchantUtils.fenToYuan(respData.getAmount().getTotal()));
            updartePayOrderDO.setPayAmount(MerchantUtils.fenToYuan(respData.getAmount().getPayer_total()));
        }

        if (respData.getPayer() != null) {
            updartePayOrderDO.setUserId(respData.getPayer().getSp_openid());
        }

        PayOrderChannelResultDto payOrderChannelResultDto = new PayOrderChannelResultDto();
        payOrderChannelResultDto.setUpdatePayOrderDO(updartePayOrderDO);
        payOrderChannelResultDto.setCallBackBody(params);
        return payOrderChannelResultDto;
    }



    @Override
    public CommonResult<PayOrderChannelResultDto> orderStatusParse(HttpServletRequest request) {
        return null;
    }

    @Override
    public CommonResult<PayOrderChannelResultDto> merAuthParse(HttpServletRequest request) {
        return null;
    }

    @Override
    public PayOrderDO closeOrderStatusParse(HttpServletRequest request, ProviderChannelConfigDO providerChannelConfigDO) {
        return null;
    }

    @Override
    public CommonResult<CsOrderNotifyDto> csOrderStatusParse(HttpServletRequest request, ProviderChannelConfigDO providerChannelConfigDO) {
        return null;
    }

    @Override
    public CommonResult<RefundOrderNotifyDto> refundStatusParse(HttpServletRequest request, ProviderChannelConfigDO providerChannelConfigDO) {

        CommonResult<RefundOrderNotifyDto> payOrderDOCommonResult = CommonResult.success();
        try {
            String paramsConfig = providerChannelConfigDO.getParamsConfig();
            // 微信配置信息
            WxPayConfig wxPayConfig = JsonUtils.parseObject(paramsConfig, WxPayConfig.class);

            String timestamp = request.getHeader("Wechatpay-Timestamp");
            String nonce = request.getHeader("Wechatpay-Nonce");
            String serialNo = request.getHeader("Wechatpay-Serial");
            String signature = request.getHeader("Wechatpay-Signature");
            log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
            String result = HttpRequestUtil.requestBodyParams(request);
            log.info("微信支付退款通知密文 {}", result);
            String plainText = null;
            // 微信公钥验证签名并解密
            if(serialNo.equals(wxPayConfig.getPayPublicId())){
                plainText = WxPayKit.verifyPublicKeyNotify(result, signature, nonce, timestamp,
                        wxPayConfig.getApiV3Key(), wxPayConfig.getPayPublicPath());
            }else if(serialNo.equals(wxPayCommonParamsHandle.getPlatSerialNumber(wxPayConfig))){
                plainText = WxPayKit.verifyNotify(serialNo,result, signature, nonce, timestamp,wxPayConfig.getApiV3Key(), wxPayConfig.getPlatformCertPath());
            }

            log.info("微信支付退款通知明文 {}", plainText);

            if (StrUtil.isEmpty(plainText)) {
                log.info("微信支付退款回调验签不通过,signature:{}", signature);
                payOrderDOCommonResult.setCode(MerchantResultCode.MERCHANT_CHANNEL_CALLBACK_SIGN_ERROR.getCode());
            }
            RefundOrderNotifyDto refundOrderNotifyDto=new RefundOrderNotifyDto();
            refundOrderNotifyDto.setCallBackBody(plainText);
            RefundOrderDO updateRefundOrderDO = new RefundOrderDO();
            WxRefundNotifyResp respData = JsonUtils.parseObject(plainText, WxRefundNotifyResp.class);
            switch (respData.getRefund_status()) {
                case "SUCCESS":
                    updateRefundOrderDO.setRefundStatus(PayOrderEnums.REFUND_STATUS.REFUND.getCode());
                    updateRefundOrderDO.setBankMsg("");
                    break;
                case "ABNORMAL":
                case "CLOSED":
                    updateRefundOrderDO.setRefundStatus(PayOrderEnums.REFUND_STATUS.FAIL.getCode());
                    break;
                default:
                    updateRefundOrderDO.setRefundStatus(PayOrderEnums.REFUND_STATUS.REFUNDING.getCode());

            }

            updateRefundOrderDO.setRefundOrderNo(respData.getOut_trade_no());
            if (respData.getAmount() != null && respData.getAmount().getPayer_total() != null) {
                updateRefundOrderDO.setActualRefundAmount(MerchantUtils.fenToYuan(respData.getAmount().getPayer_refund()));
            }
            if (StringUtils.hasText(respData.getSuccess_time())) {
                try {
                    updateRefundOrderDO.setSuccessTime(DateUtils.shanghaiDate(respData.getSuccess_time(),DateTimeFormatter.ISO_OFFSET_DATE_TIME));
                }
                catch (Exception e) {
                    log.error("微信退款订单查询返回 时间格式化错误 dateTime:{},message:{}", respData.getSuccess_time(), e.getMessage());
                }
            }
            refundOrderNotifyDto.setRefundOrder(updateRefundOrderDO);
            payOrderDOCommonResult.setData(refundOrderNotifyDto);
        }
        catch (Exception e) {
            e.printStackTrace();
            payOrderDOCommonResult.setCode(MerchantResultCode.MERCHANT_CHANNEL_CALLBACK_SIGN_ERROR.getCode());
        }

        log.info("微信收到退款回调,解析后的订单数据为 payOrderDO:{}", JsonUtils.toJsonStringNotNull(payOrderDOCommonResult.getData()));
        return payOrderDOCommonResult;
    }

    @Override
    public String channelPayCode(PayWayDO payWayDO) {
        return wxPay(payWayDO);
    }

    @Override
    public Long queryChannelPayAppid(ChannelPayOrderContext channelPayOrderContext) {
        PayWayDO payWayDO = channelPayOrderContext.getPayWayDO();
        PayOrderDO payOrderDO = channelPayOrderContext.getPayOrderDO();
        PayOrderCommonParamReq.Extra extra = channelPayOrderContext.getExtra();
        if (extra == null && StringUtils.hasText(payOrderDO.getExtra())) {
            extra = JsonUtils.parseObject(payOrderDO.getExtra(), PayOrderCommonParamReq.Extra.class);
        }
        PayEnums.PAY_TYPE payType = PayEnums.PAY_TYPE.fromByCode(payWayDO.getType());
        PayEnums.SCENE_PAY_TYPE scenePayType = PayEnums.SCENE_PAY_TYPE.fromByType(payWayDO.getSubType());
        ProviderChannelConfigDO providerChannelConfigDO = channelPayOrderContext.getProviderChannelConfigDO();
        if (extra != null && payType.getCode().equals(PayEnums.PAY_TYPE.WECHAT.getCode())) {
            switch (scenePayType) {
                case QR:
                case APP:
                case H5:
                case JSAPI:
                    return providerChannelConfigDO.getWechatDefaultPubId();
                case MINI_APP:
                    return providerChannelConfigDO.getWechatDefaultMiniId();
                default:
            }
        }

        return null;
    }
    @Override
    public String callBackResponse(CommonResult commonResult) {
        if (commonResult.isSuccess()) {
            return "{\"code\":\"SUCCESS\",\"message\":\"执行成功\"}";
        }
        else {
            return "{\"code\":\"FAIL\",\"message\":\"+" + commonResult.getMsg() + "\"}";
        }
    }

    @Override
    public boolean wechatLink(ChannelPayOrderContext channelPayOrderContext) {
        return false;
    }

    /**
     * 微信支付方式路由
     * 
     * @param payWayDO
     * @return
     */
    private String wxPay(PayWayDO payWayDO) {
        PayEnums.SCENE_PAY_TYPE scenePayType = PayEnums.SCENE_PAY_TYPE.fromByCode(payWayDO.getCode());
        if (scenePayType == null) {
            return null;
        }
        switch (scenePayType) {
            case H5:
                return WxPayExtConstant.H5;
            case APP:
                return WxPayExtConstant.APP_PAY;
            case JSAPI:
                return WxPayExtConstant.JSAPI_PAY;
            case MINI_APP:
                return WxPayExtConstant.MINI_PAY;
            case QR:
                return WxPayExtConstant.NATIVE_PAY;
        }
        return null;
    }
}
